Skip to main content

aviutl2\generic\binding/
project.rs

1use crate::load_wide_string;
2
3/// プロジェクトファイルにデータを保存・取得するための構造体。
4pub struct ProjectFile<'a> {
5    pub(crate) internal: *mut aviutl2_sys::plugin2::PROJECT_FILE,
6    _marker: std::marker::PhantomData<&'a ()>,
7}
8
9/// プロジェクトファイルのデータ取得・保存に関するエラー。
10#[derive(thiserror::Error, Debug)]
11pub enum ProjectFileError {
12    #[error("key contains null byte: {0}")]
13    KeyContainsNull(std::ffi::NulError),
14    #[error("data retrieval failed for key {0}")]
15    RetrievalFailed(String),
16    #[error("data length exceeds 4096 bytes, got {0} bytes")]
17    DataTooLarge(usize),
18    #[error("value contains null byte: {0}")]
19    ValueContainsNull(std::ffi::NulError),
20}
21
22impl<'a> ProjectFile<'a> {
23    /// 生ポインタから`ProjectFile`を作成します。
24    ///
25    /// # Safety
26    ///
27    /// - `raw`は有効な`PROJECT_FILE`ポインタである必要があります。
28    pub unsafe fn from_raw(raw: *mut aviutl2_sys::plugin2::PROJECT_FILE) -> Self {
29        Self {
30            internal: raw,
31            _marker: std::marker::PhantomData,
32        }
33    }
34
35    /// プロジェクトに保存されている文字列を取得します。
36    ///
37    /// # Errors
38    ///
39    /// - `key`にヌル文字が含まれている場合、失敗します。
40    /// - 文字列が見つからなかった場合は失敗します。
41    pub fn get_param_string(&self, key: &str) -> Result<String, ProjectFileError> {
42        let c_key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
43        unsafe {
44            let raw_str = ((*self.internal).get_param_string)(c_key.as_ptr() as _);
45            if raw_str.is_null() {
46                return Err(ProjectFileError::RetrievalFailed(key.to_string()));
47            }
48            Ok(std::ffi::CStr::from_ptr(raw_str)
49                .to_string_lossy()
50                .into_owned())
51        }
52    }
53
54    /// プロジェクトに保存されているバイナリデータを取得します。
55    ///
56    /// # Errors
57    ///
58    /// - `key`にヌル文字が含まれている場合、失敗します。
59    /// - `data` の長さが保存されているデータの長さと一致しない場合、失敗します。
60    /// - 指定されたキーに対応するデータが存在しない場合、失敗します。
61    pub fn get_param_binary(&self, key: &str, data: &mut [u8]) -> Result<(), ProjectFileError> {
62        let success = unsafe {
63            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
64            ((*self.internal).get_param_binary)(
65                key.as_ptr() as _,
66                data.as_mut_ptr() as _,
67                data.len() as _,
68            )
69        };
70        if !success {
71            return Err(ProjectFileError::RetrievalFailed(key.to_string()));
72        }
73        Ok(())
74    }
75
76    /// プロジェクトに文字列を保存します。
77    ///
78    /// # Errors
79    ///
80    /// key、valueにヌル文字が含まれている場合、失敗します。
81    pub fn set_param_string(&mut self, key: &str, value: &str) -> Result<(), ProjectFileError> {
82        let key_cstr = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
83        let value_cstr =
84            std::ffi::CString::new(value).map_err(ProjectFileError::ValueContainsNull)?;
85        unsafe {
86            ((*self.internal).set_param_string)(key_cstr.as_ptr() as _, value_cstr.as_ptr() as _);
87        }
88        Ok(())
89    }
90
91    /// プロジェクトにバイナリデータを保存します。
92    ///
93    /// # Errors
94    ///
95    /// - `data` の長さが4096バイトを超える場合、失敗します。
96    /// - `key`にヌル文字が含まれている場合、失敗します。
97    pub fn set_param_binary(&mut self, key: &str, data: &[u8]) -> Result<(), ProjectFileError> {
98        if data.len() > 4096 {
99            return Err(ProjectFileError::DataTooLarge(data.len()));
100        }
101        unsafe {
102            let key = std::ffi::CString::new(key).map_err(ProjectFileError::KeyContainsNull)?;
103            ((*self.internal).set_param_binary)(
104                key.as_ptr() as _,
105                data.as_ptr() as _,
106                data.len() as _,
107            );
108        }
109        Ok(())
110    }
111
112    /// プロジェクトに保存されているデータをすべて削除します。
113    pub fn clear_params(&mut self) {
114        unsafe { ((*self.internal).clear_params)() }
115    }
116
117    /// プロジェクトファイルのパスを取得します。
118    pub fn get_path(&self) -> Option<std::path::PathBuf> {
119        unsafe {
120            let raw_str = ((*self.internal).get_project_file_path)();
121            if raw_str.is_null() {
122                return None;
123            }
124            let path_str = load_wide_string(raw_str);
125            if path_str.is_empty() {
126                return None;
127            }
128            Some(std::path::PathBuf::from(path_str))
129        }
130    }
131}
132
133#[cfg(feature = "serde")]
134const _: () = {
135    use base64::{Engine, engine::general_purpose::STANDARD as BASE64};
136    use std::io::Read;
137
138    static NAMESPACE: &str = "--aviutl2-rs";
139    const BINARY_CHUNK_SIZE: usize = 4096;
140    // 現状制限はないが、一応4096バイトで分割しておく
141    const BASE64_CHUNK_RAW_SIZE: usize = 3072;
142
143    /// プロジェクトのシリアライズ・デシリアライズ関連のエラー。
144    #[derive(thiserror::Error, Debug)]
145    pub enum ProjectFileSerdeError {
146        #[error("serialization error: {0}")]
147        Serialization(#[from] rmp_serde::encode::Error),
148        #[error("deserialization error: {0}")]
149        Deserialization(#[from] rmp_serde::decode::Error),
150        #[error("base64 decode error: {0}")]
151        Base64Decode(#[from] base64::DecodeError),
152        #[error("zstd dompression error: {0}")]
153        Decompression(#[from] std::io::Error),
154        #[error("zlib error: {0:?}")]
155        Zlib(zlib_rs::ReturnCode),
156        #[error("project file error: {0}")]
157        ProjectFile(#[from] ProjectFileError),
158        #[error("unsupported serialization format")]
159        UnsupportedFormat,
160        #[error("invalid header format: {0}")]
161        InvalidHeaderFormat(String),
162        #[error("incomplete data retrieved for key")]
163        IncompleteData,
164    }
165
166    impl<'a> ProjectFile<'a> {
167        /// プロジェクトにデータをシリアライズして保存します。
168        ///
169        /// # Note
170        ///
171        /// 今現在の実装ではデータはMessagePackにシリアライズされ、必要に応じてzlibで圧縮された後にbase64エンコードされて保存されます。
172        ///
173        /// # Errors
174        ///
175        /// - シリアライズに失敗した場合。
176        pub fn serialize<T: serde::Serialize>(
177            &mut self,
178            key: &str,
179            value: &T,
180        ) -> Result<(), ProjectFileSerdeError> {
181            self.delete_old_chunks(key)?;
182            let base_bytes = rmp_serde::to_vec_named(value)?;
183            let compressed_bound = zlib_rs::compress_bound(base_bytes.len());
184            let mut compressed_bytes = vec![0u8; compressed_bound];
185            let (compressed_bytes, result) = zlib_rs::compress_slice(
186                &mut compressed_bytes,
187                &base_bytes,
188                zlib_rs::DeflateConfig::default(),
189            );
190            if result != zlib_rs::ReturnCode::Ok {
191                return Err(ProjectFileSerdeError::Zlib(result));
192            }
193            let num_bytes = compressed_bytes.len();
194            if num_bytes > base_bytes.len() {
195                // 圧縮後のサイズが元のサイズより大きい場合は、圧縮せずに保存する
196                self.set_param_string(
197                    key,
198                    &format!("{NAMESPACE}:serde-rmp-base64-v1:{}", base_bytes.len()),
199                )?;
200                for (i, chunk) in base_bytes.chunks(BASE64_CHUNK_RAW_SIZE).enumerate() {
201                    let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
202                    self.set_param_string(&chunk_key, &BASE64.encode(chunk))?;
203                }
204                return Ok(());
205            }
206            self.set_param_string(
207                key,
208                &format!(
209                    "{NAMESPACE}:serde-rmp-base64-zlib-v1:{},{}",
210                    num_bytes,
211                    base_bytes.len()
212                ),
213            )?;
214            for (i, chunk) in compressed_bytes.chunks(BASE64_CHUNK_RAW_SIZE).enumerate() {
215                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
216                self.set_param_string(&chunk_key, &BASE64.encode(chunk))?;
217            }
218            Ok(())
219        }
220
221        /// プロジェクトからデータをデシリアライズして取得します。
222        pub fn deserialize<T: serde::de::DeserializeOwned>(
223            &self,
224            key: &str,
225        ) -> Result<T, ProjectFileSerdeError> {
226            let header = self.get_param_string(key)?;
227            if let Ok(value) = self.decode_serde_rmp_base64_v1(key, &header) {
228                return Ok(value);
229            }
230            if let Ok(value) = self.decode_serde_zstd_v1(key, &header) {
231                return Ok(value);
232            }
233            if let Ok(value) = self.decode_serde_rmp_v1(key, &header) {
234                return Ok(value);
235            }
236            if let Ok(value) = self.decode_serde_rmp_base64_zlib_v1(key, &header) {
237                return Ok(value);
238            }
239            Err(ProjectFileSerdeError::UnsupportedFormat)
240        }
241
242        fn decode_serde_rmp_base64_v1<T: serde::de::DeserializeOwned>(
243            &self,
244            key: &str,
245            header: &str,
246        ) -> Result<T, ProjectFileSerdeError> {
247            let header_prefix = format!("{NAMESPACE}:serde-rmp-base64-v1:");
248            let num_bytes = header
249                .strip_prefix(&header_prefix)
250                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
251            let num_bytes: usize = num_bytes
252                .parse()
253                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
254            if num_bytes == 0 {
255                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
256                    header.to_string(),
257                ));
258            }
259            let chunks = self.collect_base64_chunks(num_bytes, key)?;
260            let value: T = rmp_serde::from_slice(&chunks)?;
261            Ok(value)
262        }
263
264        fn decode_serde_rmp_v1<T: serde::de::DeserializeOwned>(
265            &self,
266            key: &str,
267            header: &str,
268        ) -> Result<T, ProjectFileSerdeError> {
269            let header_prefix = format!("{NAMESPACE}:serde-rmp-v1:");
270            let num_bytes = header
271                .strip_prefix(&header_prefix)
272                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
273            let num_bytes: usize = num_bytes
274                .parse()
275                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
276            if num_bytes == 0 {
277                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
278                    header.to_string(),
279                ));
280            }
281            let chunks = self.collect_chunks(num_bytes, key)?;
282            let value: T = rmp_serde::from_slice(&chunks)?;
283            Ok(value)
284        }
285
286        fn decode_serde_rmp_base64_zlib_v1<T: serde::de::DeserializeOwned>(
287            &self,
288            key: &str,
289            header: &str,
290        ) -> Result<T, ProjectFileSerdeError> {
291            let header_prefix = format!("{NAMESPACE}:serde-rmp-base64-zlib-v1:");
292            let num_bytes = header
293                .strip_prefix(&header_prefix)
294                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
295            let (compressed_size, uncompressed_size) =
296                num_bytes
297                    .split_once(',')
298                    .ok_or(ProjectFileSerdeError::InvalidHeaderFormat(
299                        header.to_string(),
300                    ))?;
301            let compressed_size: usize = compressed_size
302                .parse()
303                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
304            let uncompressed_size: usize = uncompressed_size
305                .parse()
306                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
307            if compressed_size == 0 || uncompressed_size == 0 {
308                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
309                    header.to_string(),
310                ));
311            }
312            let compressed_chunks = self.collect_base64_chunks(compressed_size, key)?;
313            let mut decompressed_bytes = vec![0u8; uncompressed_size];
314            let (decompressed_bytes, result) = zlib_rs::decompress_slice(
315                &mut decompressed_bytes,
316                &compressed_chunks,
317                zlib_rs::InflateConfig::default(),
318            );
319            if result != zlib_rs::ReturnCode::Ok {
320                return Err(ProjectFileSerdeError::Zlib(result));
321            }
322            let value: T = rmp_serde::from_slice(decompressed_bytes)?;
323            Ok(value)
324        }
325
326        fn collect_chunks(
327            &self,
328            num_bytes: usize,
329            key: &str,
330        ) -> Result<Vec<u8>, ProjectFileSerdeError> {
331            let mut bytes = Vec::with_capacity(num_bytes);
332            let mut read_bytes = 0;
333            let mut chunk = vec![0u8; BINARY_CHUNK_SIZE];
334            for i in 0.. {
335                let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
336                let to_read = std::cmp::min(BINARY_CHUNK_SIZE, num_bytes - read_bytes);
337                chunk.resize(to_read, 0);
338                match self.get_param_binary(&chunk_key, &mut chunk) {
339                    Ok(()) => {
340                        bytes.extend_from_slice(&chunk);
341                        read_bytes += to_read;
342                        if read_bytes >= num_bytes {
343                            break;
344                        }
345                    }
346                    Err(_) => break,
347                }
348            }
349            if read_bytes != num_bytes {
350                return Err(ProjectFileSerdeError::IncompleteData);
351            }
352            Ok(bytes)
353        }
354
355        fn collect_base64_chunks(
356            &self,
357            num_bytes: usize,
358            key: &str,
359        ) -> Result<Vec<u8>, ProjectFileSerdeError> {
360            let mut bytes = Vec::with_capacity(num_bytes);
361            for i in 0.. {
362                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
363                let chunk = match self.get_param_string(&chunk_key) {
364                    Ok(chunk) => chunk,
365                    Err(_) => break,
366                };
367                let chunk = BASE64.decode(chunk)?;
368                bytes.extend_from_slice(&chunk);
369                if bytes.len() >= num_bytes {
370                    break;
371                }
372            }
373            if bytes.len() != num_bytes {
374                return Err(ProjectFileSerdeError::IncompleteData);
375            }
376            Ok(bytes)
377        }
378
379        fn decode_serde_zstd_v1<T: serde::de::DeserializeOwned>(
380            &self,
381            key: &str,
382            header: &str,
383        ) -> Result<T, ProjectFileSerdeError> {
384            let header_prefix = format!("{NAMESPACE}:serde-zstd-v1:");
385            let num_bytes = header
386                .strip_prefix(&header_prefix)
387                .ok_or(ProjectFileSerdeError::UnsupportedFormat)?;
388            let num_bytes: usize = num_bytes
389                .parse()
390                .map_err(|_| ProjectFileSerdeError::InvalidHeaderFormat(header.to_string()))?;
391            if num_bytes == 0 {
392                return Err(ProjectFileSerdeError::InvalidHeaderFormat(
393                    header.to_string(),
394                ));
395            }
396            let mut bytes = Vec::with_capacity(num_bytes);
397            let mut read_bytes = 0;
398            let mut chunk = vec![0u8; BINARY_CHUNK_SIZE];
399            for i in 0.. {
400                let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
401                let to_read = std::cmp::min(BINARY_CHUNK_SIZE, num_bytes - read_bytes);
402                chunk.resize(to_read, 0);
403                match self.get_param_binary(&chunk_key, &mut chunk) {
404                    Ok(()) => {
405                        bytes.extend_from_slice(&chunk);
406                        read_bytes += to_read;
407                        if read_bytes >= num_bytes {
408                            break;
409                        }
410                    }
411                    Err(_) => break,
412                }
413            }
414            if read_bytes != num_bytes {
415                return Err(ProjectFileSerdeError::IncompleteData);
416            }
417            let mut decoder = ruzstd::decoding::StreamingDecoder::new(&bytes[..])
418                .map_err(|e| ProjectFileSerdeError::Decompression(std::io::Error::other(e)))?;
419            let mut decompressed_bytes = vec![];
420            decoder.read_to_end(&mut decompressed_bytes)?;
421            let value: T = rmp_serde::from_slice(&decompressed_bytes)?;
422            Ok(value)
423        }
424
425        fn delete_old_chunks(&mut self, key: &str) -> Result<(), ProjectFileSerdeError> {
426            for i in 0.. {
427                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
428                match self.get_param_string(&chunk_key) {
429                    Ok(_) => {
430                        self.set_param_string(&chunk_key, "")?;
431                    }
432                    Err(_) => break,
433                }
434            }
435            for i in 0.. {
436                let chunk_key = format!("{NAMESPACE}:serde-chunk:{}:{}", key, i);
437                match self.get_param_binary(&chunk_key, &mut [0u8; 1]) {
438                    Ok(_) => {
439                        self.set_param_string(&chunk_key, "")?;
440                    }
441                    Err(_) => break,
442                }
443            }
444            for i in 0.. {
445                let chunk_key = format!("{NAMESPACE}:serde-zstd-v1:chunk:{}:{}", key, i);
446                match self.get_param_binary(&chunk_key, &mut [0u8; 1]) {
447                    Ok(_) => {
448                        self.set_param_string(&chunk_key, "")?;
449                    }
450                    Err(_) => break,
451                }
452            }
453            for i in 0.. {
454                let chunk_key = format!("{NAMESPACE}:serde-base64-chunk:{}:{}", key, i);
455                match self.get_param_string(&chunk_key) {
456                    Ok(_) => {
457                        self.set_param_string(&chunk_key, "")?;
458                    }
459                    Err(_) => break,
460                }
461            }
462            Ok(())
463        }
464    }
465};